package org.vaadin.nf4v.ext.runtime.menu.tree;

import com.vaadin.data.Item;
import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.ThemeResource;
import org.vaadin.nf4v.core.NavigableApplication;
import org.vaadin.nf4v.core.config.ContextConfig;
import org.vaadin.nf4v.core.config.NavigationConfig;
import org.vaadin.nf4v.core.config.PageConfig;
import org.vaadin.nf4v.ext.config.menu.ExtMenuUtil;
import org.vaadin.nf4v.ext.config.menu.MenuItemDescription;

/**
 * Base hierarchical container builder implementation.
 * Depends on menu configuration extension (MenuAnnotationParser should be registered and pages should be
 * annotated with @Menu annotation)
 * <p/>
 * Extend this to implement additional building aspects (name localization, security, icons handling)
 */
public class HierarchicalContainerBuilderImpl {

    /**
     * Builds entire application menu (including all contexts).
     * Note: make sure that all menu items are marked as context insensitive (@Menu(contextSensitive=false)
     * in other way automatic context switching will not work (framework will try to find page in current context).
     * Note: includeContextNodes affects only if window classes were also annotated with @Menu annotation
     *
     * @param config              navigation config
     * @param includeContextNodes true to include context nodes in menu building
     * @return menu bar
     */
    public HierarchicalContainer buildFullMenuTree(NavigationConfig config, boolean includeContextNodes) {
        HierarchicalContainer container = new HierarchicalContainer();
        initContainer(container);
        MenuItemDescription root = ExtMenuUtil.getMenuItem(config);
        if (root == null) return container;

        for (MenuItemDescription description : root.getChilds()) {
            if (includeContextNodes && description.getName() != null) {
                buildSubtree(container, description);
            } else {
                for (MenuItemDescription item : description.getChilds()) {
                    buildSubtree(container, item);
                }
            }
        }
        return container;
    }

    /**
     * Builds menu tree for context.
     * Note: includeContextNode affects only if window class were also annotated with @Menu annotation
     *
     * @param config             context config
     * @param includeContextNode true to include context node in menu building
     * @return menu bar
     */
    public HierarchicalContainer buildContextMenuTree(ContextConfig config, boolean includeContextNode) {
        HierarchicalContainer container = new HierarchicalContainer();
        initContainer(container);

        MenuItemDescription description = ExtMenuUtil.getMenuItem(config);

        if (includeContextNode && description.getName() != null) {
            buildSubtree(container, description);
        } else {
            for (MenuItemDescription item : description.getChilds()) {
                buildSubtree(container, item);
            }
        }
        return container;
    }

    /**
     * Builds menu tree for current page
     *
     * @param config            page config
     * @param includingThisPage true to include page itself as root menu item
     * @return menu bar
     */
    public HierarchicalContainer buildPageMenuTree(PageConfig config, boolean includingThisPage) {
        HierarchicalContainer container = new HierarchicalContainer();
        initContainer(container);

        MenuItemDescription description = ExtMenuUtil.getMenuItem(config);
        if (includingThisPage) {
            buildSubtree(container, description);
        } else {
            for (MenuItemDescription item : description.getChilds()) {
                buildSubtree(container, item);
            }
        }
        return container;
    }

    /**
     * Initializes container structure.
     * Override this to change structure types (e.g. change icon resource type)
     *
     * @param container hierarchical container
     */
    protected void initContainer(HierarchicalContainer container) {
        container.addContainerProperty(ItemProperties.NAME, String.class, null);
        container.addContainerProperty(ItemProperties.ICON, ThemeResource.class, null);
        container.addContainerProperty(ItemProperties.COMMAND, Runnable.class, null);
    }

    /**
     * Tree building implementation.
     *
     * @param container   hierarchical container
     * @param description starting item
     */
    protected void buildSubtree(HierarchicalContainer container, MenuItemDescription description) {
        if (!isItemAllowed(description)) {
            return;
        }
        Item menu = container.addItem(description.hashCode());
        menu.getItemProperty(ItemProperties.NAME).setValue(getItemName(description));
        if (description.isCommandItem()) {
            menu.getItemProperty(ItemProperties.COMMAND).setValue(getItemCommand(description));
        }
        if (description.getIcon() != null) {
            menu.getItemProperty(ItemProperties.ICON).setValue(getItemIcon(description));
        }

        container.setChildrenAllowed(description.hashCode(), description.getChilds().size() > 0);
        if (description.getParent() != null && description.getParent().getName() != null) {
            container.setParent(description.hashCode(), description.getParent().hashCode());
        }

        handleNewItem(container, menu, description);

        for (MenuItemDescription child : description.getChilds()) {
            buildSubtree(container, child);
        }
    }

    /**
     * Override this to change default menu names logic (e.g. to add localization)
     *
     * @param description menu item description
     * @return menu item name
     */
    protected String getItemName(MenuItemDescription description) {
        return description.getPath()[description.getPath().length - 1];
    }

    /**
     * Override this to change Resource implementation.
     * It is called only for items with not empty icon path.
     * Note: if resource type is changed also override initContainer() to set correct type.
     *
     * @param description menu item description
     * @return item icon resource
     */
    protected Resource getItemIcon(MenuItemDescription description) {
        return new ThemeResource(description.getIcon());
    }

    /**
     * Override this to change (extend) default command behaviour.
     * It is called only for page-related items (it is not called for "empty" submenu roots,
     * created during menu items path's)
     *
     * @param description menu item description
     * @return menu item command
     */
    protected Runnable getItemCommand(MenuItemDescription description) {
        return new TreeNavigationCommand(description);
    }

    /**
     * Override this to apply security restrictions or any filtering logic.
     *
     * @param description menu item description
     * @return true if item may be added to resulting tree
     */
    protected boolean isItemAllowed(MenuItemDescription description) {
        return true;
    }

    /**
     * Override this to postprocess just created items.
     *
     * @param container   hierarchical container
     * @param item        hierarchical container item
     * @param description menu item description
     */
    protected void handleNewItem(HierarchicalContainer container, Item item, MenuItemDescription description) {
    }
}
